{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 导包"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "d:\\anaconda\\envs\\bdci2024\\lib\\site-packages\\tqdm\\auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
      "  from .autonotebook import tqdm as notebook_tqdm\n"
     ]
    }
   ],
   "source": [
    "import io\n",
    "import os\n",
    "import pandas as pd\n",
    "import numpy as np\n",
    "from pdfminer.converter import TextConverter\n",
    "from pdfminer.pdfdocument import PDFTextExtractionNotAllowed\n",
    "from pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreter\n",
    "from pdfminer.pdfpage import PDFPage\n",
    "from tqdm import tqdm\n",
    "from langchain.text_splitter import CharacterTextSplitter\n",
    "from langchain.text_splitter import TokenTextSplitter\n",
    "from FlagEmbedding import FlagModel\n",
    "from sklearn.metrics.pairwise import cosine_similarity"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "# PDF解析函数\n",
    "def parsePDF(PDF_path):\n",
    "    flag = False\n",
    "    if 'AZ' in PDF_path:\n",
    "        flag = True\n",
    "    resource_manager = PDFResourceManager()\n",
    "    fake_file_handle = io.StringIO()\n",
    "    converter = TextConverter(resource_manager,fake_file_handle)\n",
    "    page_interpreter = PDFPageInterpreter(resource_manager,converter)\n",
    "    with open(PDF_path,'rb') as fh:\n",
    "        for n_page,page in enumerate(PDFPage.get_pages(fh,caching=True,check_extractable=False)):\n",
    "            if flag:\n",
    "                if n_page < 2:\n",
    "                    continue\n",
    "            page_interpreter.process_page(page)\n",
    "        text = fake_file_handle.getvalue()\n",
    "    converter.close()\n",
    "    fake_file_handle.close()\n",
    "    if text:\n",
    "        return text\n",
    "    \n",
    "# 从文本中删除指定字符串\n",
    "def drop_content_from_text(text,drop_content:list):\n",
    "    for content in drop_content:\n",
    "        text = text.replace(content,'')\n",
    "    return text"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "document_root = r'data/A榜/A_document'\n",
    "output_path = r'./result.csv'\n",
    "test_path = r'data/A榜/A_question.csv'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'本文档为2024CCFBDCI比赛用语料的一部分。部分文档使用大语言模型改写生成，内容可能与现实情况不符，可能不具备现实意义，仅允许在本次比赛中使用。【新征程上的铺路人、赋能者、护航员】系列报道之二十二：砥砺铸秋实风劲更远航——记中国联通2023年度集团级劳模风采发布时间：2024-01-10发布人：新闻宣传中心2023年，中国联通涌现出一大批先进模范人物，他们奋斗在不同岗位，有的大胆创新攻坚克难，寻求尖端技术突破，用领先科技赋能数字化建设；有的不断钻研专业技能，提高自身业务水平，用实际行动守护万家通信……他们来自天南海北，却都闪耀着一样的联通红，积极投身于以数字化网络化智能化助力中国式现代化的伟大实践，在平凡岗位上创造不凡业绩。挑最重的担子啃最硬的骨头联通华盛电商分公司办公室内的灯火彻夜不熄，这已经成为常态。在这个夏夜的凌晨12点钟，APP渠道负责人张晨正忙碌地在办公室中穿梭，她的步伐坚定有力，神情专注而严肃。为了618大促活动的顺利上线，她和团队成员们已经连续多日加班加点，几乎没有休息。张晨一边仔细审核着每一项产品政策，确保其准确无误，一边与供应商保持紧密联系，确认货源情况，以防止任何供应链上的问题可能影响到活动的正常进行。这种片刻不停歇的繁忙状态并没有让她感到疲惫，反而让她感到安心。“忙\\x0c起来，才安心，”这是张晨常挂在嘴边的一句话。这不仅反映了她对工作的投入和责任心，也展示了她对团队成员们的激励和鼓舞。2021年7月，华盛电商应集团要求承接中国联通APP终端集约化运营项目。这对于张晨和她的团队来说，是一个巨大的挑战也是一次难得的机遇。联通APP作为日活跃用户超千万的官方线上平台，如何提升用户体验，让其更加便捷、智能，这是运营者们必须攻克的难题。面对这一重任，张晨并没有退缩。相反，她迎难而上，展现出了非凡的领导力和决心。她明白，这不仅是一次职业上的考验，更是一次证明自己能力的机会。“这既是机遇也是挑战，不管多难都要把这块硬骨头给啃下来！”她在团队会议上坚定地说道。为了提升终端运营效果，张晨带领团队进行了全面的流程优化。他们加班加点，反复进行体验测试，逐一对标市场上其他优秀的终端应用，找出差距并加以改进。优化终端购买过程中可能出现的各种问题，是一项复杂且繁琐的工作，但张晨和她的团队始终保持高度的专业性和热情。在她的带领下，团队成员们齐心协力，攻坚克难，实现了联通APP终端销量的规模大幅提升。在这一过程中，张晨不仅是团队的领导者，更是每一位成员的榜样。她的勤奋和坚持，激励着团队成员们共同奋斗。每当夜深人静，办公室内仍然灯火通明，团队成员们埋头苦干的身影，正是对她工作态度的最好诠释。通过他们的不懈努力，联通APP终端集约化运营项目取得了令人瞩目的成绩，用户体验显著提升，市场竞争力大大增强。奋楫争先，匠心铸就政务行业标杆\\x0c2022年，为完成中国国家版本馆信息化协同体系建设项目，联通数科公司郭年作为项目总负责人，承担起了重任。他常年带领团队奔波于北京、广州、杭州和西安，这些城市不仅地理位置各异，而且在项目启动初期交通极为不便。面对这样的挑战，郭年毫不退缩，他经常顶着40多度的酷暑，徒步数公里到达项目现场。在那里，他组织并主持了百余次的方案研讨会，通过与各方专家的密切合作，逐步明确并完善了总分馆协同信息化建设体系。这个体系不仅实现了各版本馆之间的数字版本资源的统一管理和高效协同，还大大提升了资源的利用效率和管理水平。在党的执政能力提升工程——某部委宣传文化政务服务平台项目中，郭年再次展示了其卓越的领导力和专业能力。他深入调研了10多个处室的具体需求，全面了解和分析他们在日常工作中所遇到的各类问题和挑战。为了确保方案的切实可行，他参加了50余场技术研讨会，与各领域的专家和技术人员进行深入交流，最终制定出了一套全栈信创化云平台。这一平台采用了先进的Devops敏捷开发架构，实现了10多类系统的整合和46项政务服务事项的“一网通办、全程网办”。这一成果不仅极大地提高了政务服务的效率和便捷性，还在全国范围内树立了政务信息化建设的新标杆，并因此荣获了鼎新杯一等奖，这无疑是对郭年及其团队辛勤付出的最高褒奖。不仅在政务行业信息化建设方面，郭年还在近年来成功走通了信创化与数字化融合发展之路，提出了联通信创云能力图谱。这一图谱不仅为中宣部宣传云、国家药监云、国家农业农村部遥感云、北京市警务云等重点项目的突破奠定了坚实的基础，更为联通在信创云领域跻身第一梯队打下了坚实的基础。在这些项目中，郭年和他的团队充分展示了他们的创新能力和专业精神，通过不断的技术创新和流程优化，确保了项目的顺利实施和高效运行。\\x0c没有硝烟的战场上守好网络安全这班岗网络安全是一场没有硝烟的战争，而张文正是这场战争中的一名无名英雄。每天，他的工作都是从紧张而忙碌的状态中开始。“文哥，刚刚几个业务单元上报的安全事件，涉及多个相同的高危互联网访问行为。”这样的报告几乎每天都会出现在他的桌面上。接收、研判、上报、溯源、处置，张文已经习惯了这种高强度的工作节奏。他和他的同事们每天都要处理上千条的告警信息，这些信息背后可能隐藏着对国家和企业信息安全的巨大威胁。打好每一场保卫战！网络安全是一个没有硝烟的战场，要打好每一场保卫战，做到万无一失，“因为一失就意味着万无”。这是张文常挂在嘴边的一句话。在各类重要活动发布保障任务开始前，他就已经在脑海里一遍又一遍地模拟可能出现的攻击路径，并制定出详细的防御方案和应急处置措施。从他接受任务的那一刻起，他的脑海里就不停地思考着各种可能的情况，确保在任何紧急情况下都能够迅速有效地应对。很多“网络狙击战”，或许就是在他的键盘敲击声中完成的，一个键盘、一个回车、一通电话，这些看似简单的操作背后，却是张文和他的团队无数个日夜的辛勤付出和业务磨砺。正如他所说：“没有专业、扎实、深厚的基本功，何以保障国家的网络安全。”站好每一班值守岗！张文不仅在日常工作中尽职尽责，每逢重大活动和节庆保障任务，他更是全力以赴。“在1111过1111”，这是“双11”当天张文发的朋友圈。“值守”两字早已成为他的日常。在冬奥会的网络保障期间，张文几乎是以公司为家，参与了7*24小时的网络安全值守，足足有50多天没有回家。尽管工作繁重，他却从未有过一丝懈怠。他说：“辛苦是肯定的，但更多的是那一刻有一种重任在肩的激动和‘天降大任于斯人也’的责任。”在冬奥会期间，他和团队一起克服了无数的技术难题，确保了赛事期间网络的绝对安全和稳定。\\x0c当他看到五星红旗在领奖台上冉冉升起时，他的心中充满了自豪与成就感。不仅如此，在建党百年的庆祝活动中，张文再一次肩负起了网络安全保障的重任。他和团队成员们一起，确保了活动期间网络的畅通无阻。在庆祝活动中，当他听到年轻人高喊“请党放心”的声音时，他感到自己的付出是如此值得。而在国庆阅兵时，当看到整装待发的士兵们踏着整齐的步伐走过天安门广场时，他知道，自己和团队的努力为这些庄严时刻提供了坚实的保障。做“第一个吃螃蟹的人”点亮北京冬奥科技树2022年北京冬奥会提出了“科技冬奥”的愿景，这一愿景不仅仅是关于运动的精彩呈现，更是对未来技术的预见和应用。从2018年起，联通智网科技的辛亮便与他的团队在行业中率先开展云边协同技术架构及车联网创新场景的研究工作。他们深知，这是一次前所未有的挑战，但也是一次前所未有的机遇。“受环境条件限制，我们团队几个人只能蹲着在路边落地机柜旁调试，”辛亮回忆起那段艰难的日子，仍然记忆犹新。调试期恰逢炎炎夏日，团队每天早上8点准时到达首钢园，直到天黑才回，他们几个都晒出了健康的肤色。尽管条件艰苦，但辛亮和他的团队没有退缩，他们深知每一个细小的进步，都是向成功迈进的一大步。经过无数个日夜的奋战，团队在首钢园区成功搭建起了行业首个5G+MEC智能网联研发测试环境。这一测试环境不仅验证了5G+MEC网络在车路协同场景中的应用优势，还为整个行业推行5G+V2X融合的车路协同技术路线提供了宝贵的实践基础。为了支撑车联网全国推广，辛亮和他的团队并没有停下脚步。他们开始了更为复杂的“边缘云-中心云”两级架构研究，这一研究涉及与车企、路侧设备厂家等多个合作方的反复论证和探索试验。每一次论证，每一个实验，\\x0c都是对团队耐力和智力的双重考验。最终，团队成功在天津海教园、北京亦庄和雄安新区5G智能网联示范基地，建成了行业首个跨省互联的车联网示范基地。他们不仅实现了技术上的突破，更在行业中树立了新的标杆。辛亮和他的团队落地了行业首个“边缘云+区域云+中心云”三级云架构的车路协同服务平台，这一平台的建立，不仅为我国未来城市级的车路云一体化应用示范提供了实践经验，还为车联网的大规模商业落地提供了坚实的基础。辛亮和他的团队，通过不断的技术创新和不懈的努力，点亮了北京冬奥会的科技树，同时也为中国的车联网发展注入了新的活力。行走于行业无人区力破“卡脖子”问题“某区域5G网络覆盖咋样？”“四环内该建多少站点？”每当网络规划遇到这些复杂问题，大家都会说“找中讯院南作”。南作自2002年入职联通中讯院以来，就一直致力于移动网络的规划仿真以及相关电磁传播基础领域的研究。他的努力和贡献在业内早已广为人知。在2023年中国科协“创新达人”河南省评选活动中，南作荣获“创新达人榜样人物”称号，并被专家组誉为“行业无人区的领头羊”。2018年，国内5G网络开始大规模规划建设前夕，南作敏锐地发现网络规划设计最为基础的电磁传播模型技术主要由西方国家掌控。这些模型在计算机理论与训练校正上已经暴露出较大的缺陷。一次，与外国专家交流时，当对方提到“关于模型算法，按照我们的方法你们用就行……”这句话时，南作深感震动，暗下决心一定要做出超越国外性能的电磁传播模型算法及网络仿真方法论。为了打破国外技术垄断，实现自主创新，南作开始了一段艰苦卓绝的研发之路。他争分夺\\x0c秒、加班加点，几乎将所有的时间和精力都投入到5G网络建设方案的仿真推演方法探索中。他不仅需要制定三维射线跟踪模型的方案，还要在理论和实践中不断验证和优化。历时3个多月，他和团队先后形成了模型基础材质特征库、三维电磁传播模型算法、基于AI的无线传播环境聚类、全国电磁传播模型库等一系列创新方案。在这个过程中，南作和他的团队付出了巨大的努力和心血。每天，他们都会进行大量的模拟实验，调整参数，验证结果。他们不断地改进算法，提高模型的精度和效率。在一次次的失败和挫折中，南作没有退缩，始终坚持自己的信念，带领团队迎难而上。最终，他们成功开发出了性能超越国外同类技术的电磁传播模型算法和网络仿真方法论。这些创新成果在5G重点方案的推演与决策过程中发挥了关键作用。南作和他的团队不仅解决了5G网络规划中的诸多难题，还为中国的5G网络建设提供了坚实的技术支撑。他们的努力和成就，不仅打破了国外技术的垄断，还为中国在5G技术领域的自主创新树立了标杆。披星戴月的辛劳只为用户安心太阳还未升起就抵达工作园区，直到月亮出来才结束。这是联通资产运营公司亦庄驻地组吴永多年养成的工作习惯，雷打不动。“业主有所呼，我们有所应”“我们多一分细心，业主少一分担心；我们少一分粗心，业主多一分放心”是驻地组的服务口号，也是吴永的工作座右铭。“前阵子强降雪后气温骤降，我们每天对进出员工3000人、机动车700多辆的闸机和道杆系统的亲自检视，已成为进园后的第一个规定动作，冬季极端低温对室外设备的影响不容忽视。”\\x0c餐饮服务历来是服务保障难点，菜品出品必须经过两道把关才能给员工端上餐桌，驻地组旁站加工现场，品尝检视每一道菜品的色香味和定价，将餐饮监管做细做实。下午两点供暖锅炉房，检查动态测温小组半天的办公区测温数据，五点正常召开驻地晚例会，对当天员工反映的自行车棚拥挤和新能源车充电位不足这两个问题进行解决，七点就员工反映的路灯照度不足进行勘察。晚上八点，吴永结束了一天的工作，这时已过晚高峰，“回家路上伴着万家灯火，我的内心感到十分温馨”。砥砺铸秋实，风劲更远航。中国联通2024年工作会议上共表彰了来自全系统104名2023年度集团级劳动模范，他们是新时代奋进者的缩影。中国联通广大干部职工将以劳模先进为榜样，坚守红色通信初心，为加快推动企业高质量发展、建设世界一流企业不懈奋斗。\\x0c'"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 所有文档\n",
    "docs = os.listdir(document_root)\n",
    "parsePDF(os.path.join(document_root,docs[0]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " 92%|█████████▏| 110/120 [00:42<00:06,  1.49it/s]The PDF <_io.BufferedReader name='data/A榜/A_document\\\\AZ01.pdf'> contains a metadata field indicating that it should not allow text extraction. Ignoring this field and proceeding. Use the check_extractable if you want to raise an error in this case\n",
      " 92%|█████████▎| 111/120 [00:43<00:06,  1.42it/s]The PDF <_io.BufferedReader name='data/A榜/A_document\\\\AZ02.pdf'> contains a metadata field indicating that it should not allow text extraction. Ignoring this field and proceeding. Use the check_extractable if you want to raise an error in this case\n",
      " 93%|█████████▎| 112/120 [00:44<00:07,  1.01it/s]The PDF <_io.BufferedReader name='data/A榜/A_document\\\\AZ03.pdf'> contains a metadata field indicating that it should not allow text extraction. Ignoring this field and proceeding. Use the check_extractable if you want to raise an error in this case\n",
      " 94%|█████████▍| 113/120 [00:45<00:07,  1.03s/it]The PDF <_io.BufferedReader name='data/A榜/A_document\\\\AZ04.pdf'> contains a metadata field indicating that it should not allow text extraction. Ignoring this field and proceeding. Use the check_extractable if you want to raise an error in this case\n",
      " 95%|█████████▌| 114/120 [00:46<00:06,  1.00s/it]The PDF <_io.BufferedReader name='data/A榜/A_document\\\\AZ05.pdf'> contains a metadata field indicating that it should not allow text extraction. Ignoring this field and proceeding. Use the check_extractable if you want to raise an error in this case\n",
      " 96%|█████████▌| 115/120 [00:48<00:06,  1.23s/it]The PDF <_io.BufferedReader name='data/A榜/A_document\\\\AZ06.pdf'> contains a metadata field indicating that it should not allow text extraction. Ignoring this field and proceeding. Use the check_extractable if you want to raise an error in this case\n",
      " 97%|█████████▋| 116/120 [00:49<00:05,  1.29s/it]The PDF <_io.BufferedReader name='data/A榜/A_document\\\\AZ07.pdf'> contains a metadata field indicating that it should not allow text extraction. Ignoring this field and proceeding. Use the check_extractable if you want to raise an error in this case\n",
      " 98%|█████████▊| 117/120 [00:51<00:03,  1.22s/it]The PDF <_io.BufferedReader name='data/A榜/A_document\\\\AZ08.pdf'> contains a metadata field indicating that it should not allow text extraction. Ignoring this field and proceeding. Use the check_extractable if you want to raise an error in this case\n",
      " 98%|█████████▊| 118/120 [00:52<00:02,  1.30s/it]The PDF <_io.BufferedReader name='data/A榜/A_document\\\\AZ09.pdf'> contains a metadata field indicating that it should not allow text extraction. Ignoring this field and proceeding. Use the check_extractable if you want to raise an error in this case\n",
      " 99%|█████████▉| 119/120 [00:54<00:01,  1.48s/it]The PDF <_io.BufferedReader name='data/A榜/A_document\\\\AZ10.pdf'> contains a metadata field indicating that it should not allow text extraction. Ignoring this field and proceeding. Use the check_extractable if you want to raise an error in this case\n",
      "100%|██████████| 120/120 [00:55<00:00,  2.15it/s]\n"
     ]
    }
   ],
   "source": [
    "# 将所有文档内容拼接在一起\n",
    "knowledge = []\n",
    "drop_content = ['\\x0c',\n",
    "                '本文档为2024CCFBDCI比赛用语料的一部分。部分文档使用大语言模型改写生成，内容可能与现实情况不符，可能不具备现实意义，仅允许在本次比赛中使用。']\n",
    "for doc in tqdm(docs):\n",
    "    doc_path = os.path.join(document_root,doc)\n",
    "    text = drop_content_from_text(parsePDF(doc_path),drop_content)\n",
    "    knowledge.append(text)\n",
    "knowledge = ''.join(knowledge)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 将文本分割成多个块\n",
    "token_splitter = TokenTextSplitter(chunk_size=500, chunk_overlap=50)\n",
    "chunks = token_splitter.split_text(knowledge)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 自定义向量数据库\n",
    "class KnowledgeDataBase:\n",
    "    def __init__(self,chunks) -> None:\n",
    "        self.model = FlagModel('BAAI/bge-large-zh-v1.5',\n",
    "                  query_instruction_for_retrieval=\"Represent this sentence for searching relevant passages:\",\n",
    "                  use_fp16=True)\n",
    "        self.chunks = [drop_content_from_text(chunk,'�') for chunk in chunks]\n",
    "        self.db = self.create_db(chunks)\n",
    "\n",
    "    # 创建数据库\n",
    "    def create_db(self,chunks):\n",
    "        print('create db..')\n",
    "        db = []\n",
    "        for chunk in tqdm(chunks):\n",
    "            db.append(self.model.encode(chunk))\n",
    "        return db\n",
    "\n",
    "    # 查询最接近的n个文本块\n",
    "    def search(self,query,n=5):\n",
    "        query_embedding = self.model.encode(query)\n",
    "        similarity_scores = [(doc_id, cosine_similarity(query_embedding.reshape(1,-1), doc.reshape(1,-1))[0][0]) \n",
    "                            for doc_id, doc in enumerate(self.db)]\n",
    "        top_n_results = sorted(similarity_scores, key=lambda x: x[1], reverse=True)[:n]\n",
    "        return top_n_results\n",
    "    "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "create db..\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "  3%|▎         | 94/3449 [00:52<31:24,  1.78it/s]"
     ]
    }
   ],
   "source": [
    "kdb = KnowledgeDataBase(chunks)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 62,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 读取问题并选取最接近的答案和编码\n",
    "df_test = pd.read_csv(test_path)\n",
    "df_test['id'] = df_test['question'].apply(lambda x:[id for id,score in kdb.search(x,n=1)][0])\n",
    "df_test['answer'] = df_test['id'].apply(lambda x:kdb.chunks[x])\n",
    "df_test['embedding'] = df_test['id'].apply(lambda x:str(list(kdb.db[x]))[1:-2])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 63,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0     1115\n",
       "1     1623\n",
       "2     1504\n",
       "3     1506\n",
       "4     1571\n",
       "      ... \n",
       "95     817\n",
       "96     821\n",
       "97     685\n",
       "98     694\n",
       "99     699\n",
       "Name: id, Length: 100, dtype: int64"
      ]
     },
     "execution_count": 63,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df_test['id']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df_test['question'][0],df_test['answer'][0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 64,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 保存结果\n",
    "df_test.drop(['id'],axis=1,inplace=True)\n",
    "df_test.to_csv('result.csv',index=False)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "base",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.19"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
